﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;
using VSOLE = Microsoft.VisualStudio.OLE.Interop;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using EnvDTE;
using System.Diagnostics;
using System.IO;
using System.Collections;
using NBusiness.ESharp.Compiler;
using NBusiness.CodeDom;
using NBusiness.CodeDom.Compiler;
using NBusiness.ESharp;
using System.Windows.Forms;
using System.Text.RegularExpressions;
using NBusiness.VisualStudio.Commands;

namespace NBusiness.VisualStudio
{
    /// <summary>
    /// This scanner is used in the Colorizer class for colorization 
    /// although the scanner can also be used for getting token types 
    /// and triggers as a prelude to a more complex parsing operation. 
    /// </summary>
    /// <see cref="http://msdn2.microsoft.com/en-us/library/microsoft.visualstudio.package.iscanner.aspx"/>
    class ESharpScanner : IScanner
    {
        ESharpLanguageService _languageService;
        EntityParser _parser;
        EntityElement[] _codeElements = new EntityElement[] { };
        EntityElement[] _tokens = new EntityElement[] { };
        List<TextLineChange> _lineChanges = new List<TextLineChange>();

        object SyncRoot = new object();
        bool _needsRefreshing = true;
        IVsTextLines _buffer;
        int _currentToken;
        int _line;


        public static Regex Quoted = new Regex("^(\"[^\"\r\n]*\")$");
        public static Regex Literal = new Regex("^(['].[']|[0-9]+)$");
           
        public ESharpScanner(ESharpLanguageService languageService, IVsTextLines buffer)
        {
            _languageService = languageService;
            _buffer = buffer;

            ESharpProvider provider = new ESharpProvider();
            _parser = provider.GetParser();
        }

        #region IScanner Members

        public bool ScanTokenAndProvideInfoAboutIt(TokenInfo tokenInfo, ref int state)
        {
            state = 0;                       
            if (_needsRefreshing)
                Refresh();

            if (_tokens.Length > 0 && 
                _codeElements.Length > 0)
            {
                EntityElement current = null;
                lock (_tokens)
                    current = _tokens[_currentToken++];

                EntityElement element = null;
                EntityElement previous = null;

                lock (_codeElements)
                    element = _codeElements.FirstOrDefault(e => e.Line == _line && e.Column == current.Column);

                if (element != null)
                {
                    lock (_codeElements)
                        previous = _codeElements.FirstOrDefault(e => e.EndOffset == element.BeginOffset - 1);

                    if (previous != null)
                    {
                        tokenInfo.StartIndex = current.Column;
                        tokenInfo.EndIndex = current.Column + element.Value.Length - 1;
                        tokenInfo.Type = GetTokenType(element);
                        tokenInfo.Color = GetTokenColor(tokenInfo.Type);
                        tokenInfo.Trigger = (previous != null ? GetTokenTrigger(previous.Type) : TokenTriggers.None);
                    }
                }
                else System.Diagnostics.Debug.WriteLine("parse error");
            }

            return _currentToken < _tokens.Length;
        }

        public void SetSource(string source, int offset)
        {
            string code = source.Substring((int)Math.Min(offset, source.Length));
            _currentToken = 0;
            lock(_tokens)
                _tokens = _parser.GetElements(new EntityFile { Data = code });
        }

        #endregion

        private TokenTriggers GetTokenTrigger(EntityElementType type)
        {
            TokenTriggers trigger = TokenTriggers.None;

            if (type == ESharpEntityElementTypes.FieldDeclaration ||
                type == ESharpEntityElementTypes.FieldAutoProperty ||
                type == ESharpEntityElementTypes.FieldIdProperty ||
                type == ESharpEntityElementTypes.FieldNullableProperty ||
                type == ESharpEntityElementTypes.FieldReadOnly ||
                type == ESharpEntityElementTypes.AccessDeclaration)
            {
                trigger = TokenTriggers.MemberSelect;
            }

            return trigger;
        }

        private TokenColor GetTokenColor(TokenType type)
        {
            switch (type)
            {
                case TokenType.Comment:
                    return TokenColor.Comment;
                case TokenType.Keyword:
                    return TokenColor.Keyword;
                case TokenType.String:
                    return TokenColor.String;
                case TokenType.Identifier:
                    return TokenColor.Identifier;
                case (TokenType)11:
                    return (TokenColor)7;
                case (TokenType)12:
                    return (TokenColor)8;
                default:
                    return TokenColor.Text;
            }
        }

        private TokenType GetTokenType(EntityElement element)
        {
            TokenType token = TokenType.Unknown;
            if (element.Type == ESharpEntityElementTypes.WhiteSpace)
            {
                token = TokenType.WhiteSpace;
            }
            else if (element.Type == ESharpEntityElementTypes.Comment)
            {
                token = TokenType.Comment;
            }
            else if (element.Type == ESharpEntityElementTypes.StatementEnd ||
                element.Type == ESharpEntityElementTypes.UsingTypeJoin ||
                element.Type == ESharpEntityElementTypes.FamilyJoin ||
                element.Type == ESharpEntityElementTypes.EntityTemplateSeparator ||
                element.Type == ESharpEntityElementTypes.EntityTemplateTypeJoin ||
                element.Type == ESharpEntityElementTypes.ActionTypeJoin || 
                element.Type == ESharpEntityElementTypes.GroupBegin ||
                element.Type == ESharpEntityElementTypes.GroupEnd ||
                element.Type == ESharpEntityElementTypes.ModelInheritTypeNameJoin ||
                element.Type == ESharpEntityElementTypes.RelationshipSplit ||
                element.Type == ESharpEntityElementTypes.RelationshipAssignTo ||
                element.Type == ESharpEntityElementTypes.ValidateArgSplit)
            {
                token = TokenType.Delimiter;
            }
            else if (
                element.Type == ESharpEntityElementTypes.AccessDeclaration ||
                element.Type == ESharpEntityElementTypes.ActionDeclaration ||
                element.Type == ESharpEntityElementTypes.AuthorizeDeclaration ||
                element.Type == ESharpEntityElementTypes.EntityDeclaration ||
                element.Type == ESharpEntityElementTypes.FamilyDeclaration ||
                element.Type == ESharpEntityElementTypes.FieldDeclaration ||
                element.Type == ESharpEntityElementTypes.RelationshipDeclaration ||
                element.Type == ESharpEntityElementTypes.UsingDeclaration ||
                element.Type == ESharpEntityElementTypes.ModelDeclaration ||
                element.Type == ESharpEntityElementTypes.ModelInherit ||
                element.Type == ESharpEntityElementTypes.ModelInheritType ||
                element.Type == ESharpEntityElementTypes.ValidateDeclaration ||
                element.Type == ESharpEntityElementTypes.RelationshipType ||
                element.Type == ESharpEntityElementTypes.FieldType ||
                element.Type == ESharpEntityElementTypes.FieldIdProperty ||
                element.Type == ESharpEntityElementTypes.FieldNullableProperty ||
                element.Type == ESharpEntityElementTypes.FieldReadOnly ||
                element.Type == ESharpEntityElementTypes.FieldAutoProperty)
            {
                token = TokenType.Keyword;
            }
            else if (
                element.Type == ESharpEntityElementTypes.AccessField ||
                element.Type == ESharpEntityElementTypes.ActionName ||
                element.Type == ESharpEntityElementTypes.EntityName ||
                element.Type == ESharpEntityElementTypes.ModelName ||
                element.Type == ESharpEntityElementTypes.ModelBaseEntity ||
                element.Type == ESharpEntityElementTypes.ModelInheritTypeName ||
                element.Type == ESharpEntityElementTypes.FamilyName ||
                element.Type == ESharpEntityElementTypes.FieldName ||
                element.Type == ESharpEntityElementTypes.RelationshipEntity ||
                element.Type == ESharpEntityElementTypes.RelationshipField ||
                element.Type == ESharpEntityElementTypes.RelationshipName ||
                element.Type == ESharpEntityElementTypes.RelationshipAssignTo ||
                element.Type == ESharpEntityElementTypes.ValidateProperty ||
                element.Type == ESharpEntityElementTypes.UsingType ||
                element.Type == ESharpEntityElementTypes.EntityTemplateType ||
                element.Type == ESharpEntityElementTypes.ModelInheritType)
            {
                token = TokenType.Identifier;
            }
            else if (
                element.Type == ESharpEntityElementTypes.EntityAs ||
                element.Type == ESharpEntityElementTypes.RelationshipAs ||
                element.Type == ESharpEntityElementTypes.RelationshipWith ||
                element.Type == ESharpEntityElementTypes.RelationshipOn)
            {
                token = (TokenType)11;
            }
            else if (
                element.Type == EntityElementType.Unknown)
            {
                token = (TokenType)12;
            }
            else
            {
                if (Literal.IsMatch(element.Value))
                    token = TokenType.Literal;
                else if (Quoted.IsMatch(element.Value))
                    token = TokenType.String;
            }

            return token;
        }

        private string GetBufferText()
        {
            StringBuilder sb = new StringBuilder();
            int count = 0;
            _buffer.GetLineCount(out count);
            for (int x = 0; x < count; x++)
            {
                int length = 0;
                _buffer.GetLengthOfLine(x, out length);

                string buffer;
                _buffer.GetLineText(x, 0, x, length, out buffer);

                sb.AppendLine(buffer);
            }

            return sb.ToString();
        }

        internal void Refresh()
        {
            _needsRefreshing = false;

            string code = GetBufferText();

            EntityElement[] elements = _parser.GetElements(new EntityFile { Data = code });
            
            lock (_codeElements)
                _codeElements = elements;

            RefreshScannerCommand command = new RefreshScannerCommand(elements, _parser);
            CommandManager.Queue(command);
        }

        internal void SetLine(int line)
        {
            _line = line;
        }
    }
}
